// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <dlfcn.h>
#include <securec.h>
#include <fcntl.h>
#include "CasJniBridge.h"
#include "CasController.h"
#include "CasConf.h"
#include "CasTcpSocket.h"
#include "CasMsgCode.h"
#include "CasExtVideoDataPipe.h"

using namespace std;

const int BITRATE_MAX = 10000000;
const int BITRATE_MIN = 100000;
const int WIDTH_MAX = 4096;
const int WIDTH_MIN = 240;
const int HEIGHT_MAX = 4096;
const int HEIGHT_MIN = 240;
const int TIMES = 8;
const int FRAME_RATE_MIN = 10;
const int FRAME_RATE_MAX = 60;
const std::string CLIENT_TYPE = "1";

CasController *CasController::g_instance = nullptr;
CasController *CasController::GetInstance()
{
    if (g_instance == nullptr) {
        g_instance = new (std::nothrow) CasController();
        if (g_instance == nullptr) {
            ERR("Failed to new CasController.");
            return nullptr;
        }
    }
    return g_instance;
}

bool CasController::DestroyInstance()
{
    if (g_instance != nullptr) {
        delete g_instance;
        g_instance = nullptr;
        return true;
    }
    INFO("Instance already destroyed.");
    return true;
}

CasController::CasController()
{
    m_videoDecodeThread = nullptr;
    m_videoPacketStream = nullptr;
    m_audioPacketStream = nullptr;
    m_orientationStream = nullptr;
    m_virtualDeviceStream = nullptr;
    m_controlStream = nullptr;
    m_channelStream = nullptr;
    m_imeDataStream = nullptr;
    m_cmdController = nullptr;
    m_streamParseThread = nullptr;
    m_heartbeatThread = nullptr;
    m_heartbeatController = nullptr;
    m_casClientSocket = nullptr;
    m_streamBuildSender = nullptr;
    m_streamParser = nullptr;
    m_state = INIT;
    m_touch = nullptr;
    cmdCallBack = nullptr;
    m_orientation = 0;
    m_frameType = FrameType::H264;
    m_rotationDegrees = 0;
}

CasController::~CasController()
{
    m_videoDecodeThread = nullptr;
    m_videoPacketStream = nullptr;
    m_audioPacketStream = nullptr;
    m_orientationStream = nullptr;
    m_virtualDeviceStream = nullptr;
    m_controlStream = nullptr;
    m_channelStream = nullptr;
    m_imeDataStream = nullptr;
    m_cmdController = nullptr;
    m_streamParseThread = nullptr;
    m_heartbeatThread = nullptr;
    m_controlThread = nullptr;
    m_heartbeatController = nullptr;
    m_casClientSocket = nullptr;
    m_streamBuildSender = nullptr;
    m_streamParser = nullptr;
    m_state = INIT;
    m_touch = nullptr;
    cmdCallBack = nullptr;
    m_orientation = 0;
}

void CasController::SetJniConf(string key, string value)
{
    std::lock_guard<std::mutex> lockGuard(m_jniConfLock);
    this->m_jniConf[key] = value;
}

JNIState CasController::GetState()
{
    return this->m_state;
}

void CasController::SetState(JNIState state)
{
    this->m_state = state;
}

bool CasController::Start(ANativeWindow *nativeWindow, bool isHome)
{
    m_orientation = 0;
    m_nativeWindow = nativeWindow;
    bool res = false;
    if (isHome && this->GetState() != STOPPED && m_casClientSocket->GetStatus() != SOCKET_STATUS_RUNNING) {
        res = Reconnect();
        if (res) {
            CreateDecWorker(m_nativeWindow, m_needVideoDecode);
            StartDecWorker(!m_retainVideoDecode);
            return true;
        } else {
            NotifyCommand(CAS_CONNECT_LOST, CasMsgCode::GetMsg(CAS_CONNECT_LOST));
            return false;
        }
    }
    std::lock_guard<std::mutex> lockGuard(this->m_lock);
    if (isHome) {
        return ProcessEnterForeground(m_nativeWindow);
    }

    if (this->GetState() == STOPPED) {
        this->SetState(INIT);
    }

    m_conf.parseConf(m_jniConf);
    m_ticket = m_conf.ticket;
    m_sessionId = m_conf.sessionId;
    m_ip = TransIp(m_conf.ip.c_str());
    m_port = (unsigned short)m_conf.port;
    m_encryptedData = m_conf.encryptedData;
    m_verifyData = m_conf.verifyData;
    m_authTs = m_conf.authTs;
    m_aesIv = m_conf.aesIv;
    m_clientType = CLIENT_TYPE;
    m_maxDisconnectDuration = CalcMaxDisconnectDuration(m_conf.backgroundTimeout);

    if (m_mediaConfig.find(KEY_FRAME_TYPE) != m_mediaConfig.end()) {
        m_frameType = m_mediaConfig[KEY_FRAME_TYPE] == "h264" ? FrameType::H264 : FrameType::H265;
    }

    res = InitDataStream();
    if (!res) {
        CloseDataStream();
        return false;
    }

    res = CreateWorkers();
    if (!res) {
        DestroyWorkers();
        CloseDataStream();
        return false;
    }

    res = BuildConnection();
    if (!res) {
        ERR("Failed to build connection");
        DestroyWorkers();
        CloseDataStream();
        return false;
    }

    StartWorkers();

    if (!SendStartCmd()) {
        ERR("Failed to send start command");
        return false;
    }
    return true;
}

bool CasController::IsValidMediaConfig(map<string, string> mediaConfig)
{
    if (mediaConfig.empty()) {
        ERR("Media config is empty.");
        return false;
    }

    if (mediaConfig.find(KEY_BITRATE) != mediaConfig.end()) {
        uint32_t bitrate = static_cast<uint32_t>(atoi(mediaConfig[KEY_BITRATE].c_str()));
        if (bitrate < BITRATE_MIN || bitrate > BITRATE_MAX) {
            ERR("Bitrate is invalid, value is %u.", bitrate);
            return false;
        }
    }

    if (mediaConfig.find(KEY_FRAME_RATE) != mediaConfig.end()) {
        uint32_t frameRate = static_cast<uint32_t>(atoi(mediaConfig[KEY_FRAME_RATE].c_str()));
        if (frameRate < FRAME_RATE_MIN || frameRate > FRAME_RATE_MAX || frameRate % 10 != 0) {
            ERR("Frame rate is invalid, value is %u.", frameRate);
            return false;
        }
    }

    // 校验虚拟宽高，虚拟宽高需满足同时设置或同时未设置，且大于等于240，小于等于4096，与8对齐
    bool containsStreamWidth = mediaConfig.find(KEY_STREAM_WIDTH) != mediaConfig.end();
    bool containsStreamHeight = mediaConfig.find(KEY_STREAM_HEIGHT) != mediaConfig.end();
    if (containsStreamWidth && containsStreamHeight) {
        uint32_t streamWidth = static_cast<uint32_t>(atoi(mediaConfig[KEY_STREAM_WIDTH].c_str()));
        uint32_t streamHeight = static_cast<uint32_t>(atoi(mediaConfig[KEY_STREAM_HEIGHT].c_str()));
        if (streamWidth > streamHeight || streamWidth < WIDTH_MIN || streamWidth > WIDTH_MAX ||
            streamWidth % TIMES != 0 || streamHeight < HEIGHT_MIN || streamHeight > HEIGHT_MAX ||
            streamHeight % TIMES != 0) {
            ERR("Stream width or Stream height is invalid, Stream width %u Stream height %u", streamWidth, streamHeight);
            return false;
        }
    } else if (containsStreamWidth || containsStreamHeight) {
        ERR("Stream width or Stream height is not included");
        return false;
    }
    return true;
}

string CasController::CalcMaxDisconnectDuration(string backgroundTimeout)
{
    int maxDisconnectDuration = 60;
    int timeout = atoi(backgroundTimeout.c_str());
    if (timeout > 120) {
        maxDisconnectDuration = timeout - 60;
    }
    return to_string(maxDisconnectDuration);
}

bool CasController::Stop(bool isHome)
{
    std::lock_guard<std::mutex> lockGuard(this->m_lock);
    if (this->GetState() == STOPPED) {
        INFO("Current state is stopped.");
        return true;
    }
    if (isHome) {
        ProcessEnterBackground();
        return true;
    }

    map<string, string> parameters = { { KEY_COMMAND, CMD_STOP_APP } };
    bool res = SendCommand(parameters);
    if (!res) {
        ERR("Failed to send stop command.");
    }

    usleep(200000);
    this->SetState(STOPPED);

    DestroyWorkers();
    StopDecWorker(!m_retainVideoDecode);
    CloseDataStream();

    m_isNotifyFirstFrame = false;
    m_rotationDegrees = 0;
    m_orientation = 0;
    m_mediaConfig.clear();
    m_jniConf.clear();
    return true;
}

bool CasController::Release()
{
    std::lock_guard<std::mutex> lockGuard(this->m_lock);
    if (this->GetState() == STOPPED) {
        INFO("Release failed because phone already stop.");
        return false;
    }

    DestroyWorkers();
    StopDecWorker(m_retainVideoDecode);
    ClearDataStream();
    return true;
}

bool CasController::Reconnect()
{
    bool res = Release();
    if (!res) {
        ERR("Reconnect fail because release resource failed.");
        return false;
    }

    std::lock_guard<std::mutex> lockGuard(this->m_lock);
    if (this->GetState() == STOPPED) {
        INFO("Reconnect failed because phone already stop.");
        return false;
    }

    res = CreateWorkers();
    if (!res) {
        DestroyWorkers();
        return false;
    }

    NotifyCommand(CAS_RECONNECTING, CasMsgCode::GetMsg(CAS_RECONNECTING));
    int connectRes = m_casClientSocket->Connect();
    if (connectRes != 0) {
        ERR("Failed to build Reconnect socket state %d.", m_casClientSocket->GetStatus());
        this->SetState(CONNECTION_FAILURE);
        return false;
    }

    if (m_sessionId.empty()) {
        ERR("SessionId is empty.");
        NotifyCommand(CAS_RECONNECT_PARAMETER_INVALID, CasMsgCode::GetMsg(CAS_RECONNECT_PARAMETER_INVALID));
        return false;
    }
    this->SetState(CONNECTED);
    NotifyCommand(CAS_RECONNECT_SUCCESS, CasMsgCode::GetMsg(CAS_RECONNECT_SUCCESS));

    std::string reconnectCmd = CMD_RECONNECT;
    map<string, string> parameters = { { KEY_COMMAND, reconnectCmd }, { KEY_SESSION_ID, m_sessionId } };

    res = SendCommand(parameters);
    if (!res) {
        ERR("Failed to send reconnect command");
        return false;
    }

    StartWorkers();
    StartDecWorker(m_retainVideoDecode);
    return true;
}

bool CasController::CreateWorkers()
{
    m_casClientSocket = new (std::nothrow) CasTcpClientSocket(m_ip, m_port);
    if (m_casClientSocket == nullptr) {
        ERR("Failed to new client socket.");
        return false;
    }

    m_streamBuildSender = new (std::nothrow) CasStreamBuildSender(m_casClientSocket);
    if (m_streamBuildSender == nullptr) {
        ERR("Failed to new stream build sender.");
        return false;
    }

    m_streamParser = CasStreamRecvParser::GetInstance();
    if (m_streamParser == nullptr) {
        ERR("Failed to get stream parser.");
        return false;
    }

    m_streamParseThread = new (std::nothrow) CasStreamParseThread(m_casClientSocket, m_streamParser);
    if (m_streamParseThread == nullptr) {
        ERR("Failed to new stream parse thread.");
        return false;
    }

    m_heartbeatController = new (std::nothrow) CasHeartbeatController(m_streamBuildSender);
    if (m_heartbeatController == nullptr) {
        ERR("Failed to new heartbeat controller.");
        return false;
    }

    m_heartbeatThread = new (std::nothrow) CasHeartbeatThread(m_heartbeatController, m_casClientSocket);
    if (m_heartbeatThread == nullptr) {
        ERR("Failed to new heartbeat thread.");
        return false;
    }

    m_cmdController = new (std::nothrow) CasCmdController(m_streamBuildSender);
    if (m_cmdController == nullptr) {
        ERR("Failed to new cmd controller.");
        return false;
    }
    m_cmdController->RegisterStateChangeListener(this);

    m_controlThread = new (std::nothrow) CasCmdControlThread(m_cmdController);
    if (m_controlThread == nullptr) {
        ERR("Failed to new cmd controller.");
        return false;
    }
    m_controlThread->SetControlPktHandle(m_controlStream);

    m_streamParser->SetServiceHandle(CasMsgType::HeartBeat, m_heartbeatController);
    m_streamParser->SetServiceHandle(CasMsgType::CmdControl, m_controlStream);
    m_streamParser->SetServiceHandle(CasMsgType::Orientation, m_orientationStream);
    m_streamParser->SetServiceHandle(CasMsgType::Video, m_videoPacketStream);
    m_streamParser->SetServiceHandle(CasMsgType::Audio, m_audioPacketStream);
    m_streamParser->SetServiceHandle(CasMsgType::Channel, m_channelStream);
    m_streamParser->SetServiceHandle(CasMsgType::VirtualDevice, m_virtualDeviceStream);
    m_streamParser->SetServiceHandle(CasMsgType::ImeData, m_imeDataStream);

    m_touch = new (std::nothrow) CasTouch(m_casClientSocket);
    if (m_touch == nullptr) {
        ERR("Failed to new touch.");
        return false;
    }

    return true;
}

bool CasController::StartWorkers()
{
    m_streamParseThread->Start();
    m_heartbeatThread->Start();
    m_controlThread->Start();
    INFO("Succeed to start workers");
    return true;
}

bool CasController::DestroyWorkers()
{
    if (m_streamParseThread != nullptr) {
        m_streamParseThread->Stop();
        delete m_streamParseThread;
        m_streamParseThread = nullptr;
    }

    if (m_streamParser != nullptr) {
        CasStreamRecvParser::DestroyInstance();
        m_streamParser = nullptr;
    }

    if (m_touch != nullptr) {
        delete m_touch;
        m_touch = nullptr;
    }

    if (m_heartbeatThread != nullptr) {
        m_heartbeatThread->Exit();
        delete m_heartbeatThread;
        m_heartbeatThread = nullptr;
    }

    if (m_heartbeatController != nullptr) {
        m_heartbeatController->StopHandle();
        delete m_heartbeatController;
        m_heartbeatController = nullptr;
    }

    if (m_controlThread != nullptr) {
        m_controlThread->Exit();
        delete m_controlThread;
        m_controlThread = nullptr;
    }

    if (m_cmdController != nullptr) {
        delete m_cmdController;
        m_cmdController = nullptr;
    }

    if (m_streamBuildSender != nullptr) {
        delete m_streamBuildSender;
        m_streamBuildSender = nullptr;
    }

    if (m_casClientSocket != nullptr) {
        delete m_casClientSocket;
        m_casClientSocket = nullptr;
    }

    INFO("Succeed to destroy workers");
    return true;
}

bool CasController::BuildConnection()
{
    NotifyCommand(CAS_CONNECTING, CasMsgCode::GetMsg(CAS_CONNECTING));
    int connectRes = m_casClientSocket->Connect();
    if (connectRes == -1) {
        this->SetState(CONNECTION_FAILURE);
        NotifyCommand(CAS_SERVER_UNREACHABLE, CasMsgCode::GetMsg(CAS_SERVER_UNREACHABLE));
        return false;
    } else if (connectRes == -2) {
        this->SetState(CONNECTION_FAILURE);
        NotifyCommand(CAS_RESOURCE_IN_USING, CasMsgCode::GetMsg(CAS_RESOURCE_IN_USING));
        return false;
    }
    NotifyCommand(CAS_CONNECT_SUCCESS, CasMsgCode::GetMsg(CAS_CONNECT_SUCCESS));
    this->SetState(CONNECTED);
    return true;
}

bool CasController::SetMediaConfig(map<string, string> mediaConfig)
{
    if (!IsValidMediaConfig(mediaConfig)) {
        ERR("Media config is invalid");
        return false;
    }

    if (this->GetState() == INIT || this->GetState() == STOPPED) {
        INFO("Init media config");
        this->m_mediaConfig = mediaConfig;
        return true;
    }

    std::string setMediaConfigCmd = CMD_SET_MEDIA_CONFIG;
    string mediaConfigStr = CasAppCtrlCmdUtils::MakeCommand(mediaConfig, SUB_COMMAND_SEPARATOR);
    map<string, string> parameters = {
        { KEY_COMMAND, setMediaConfigCmd },
        { KEY_MEDIA_CONFIG, mediaConfigStr },
    };
    bool res = SendCommand(parameters);
    if (!res) {
        ERR("Failed to send set media config command");
        return false;
    }
    return true;
}

bool CasController::SendCommand(map<string, string> parameters)
{
    if ((m_casClientSocket == nullptr) || (m_casClientSocket->GetStatus() != SOCKET_STATUS_RUNNING)) {
        ERR("Failed to send command because socket status not running.");
        return false;
    }
    bool sendCmdRes = m_cmdController->SendCtrlCmd(parameters);
    if (!sendCmdRes) {
        ERR("Failed to send command.");
        return false;
    }
    return true;
}

bool CasController::InitDataStream()
{
    m_audioPacketStream = new (std::nothrow) CasDataPipe();
    if (m_audioPacketStream == nullptr) {
        ERR("Failed to new audio packet stream.");
        return false;
    }

    m_videoPacketStream = new (std::nothrow) CasExtVideoDataPipe();
    if (m_videoPacketStream == nullptr) {
        ERR("Failed to new video packet stream.");
        return false;
    }

    m_orientationStream = new (std::nothrow) CasDataPipe();
    if (m_orientationStream == nullptr) {
        ERR("Failed to new orientation packet stream.");
        return false;
    }

    m_controlStream = new (std::nothrow) CasDataPipe();
    if (m_controlStream == nullptr) {
        ERR("Failed to new control packet stream.");
        return false;
    }

    m_channelStream = new (std::nothrow) CasDataPipe();
    if (m_channelStream == nullptr) {
        ERR("Failed to new channel packet stream.");
        return false;
    }

    m_virtualDeviceStream = new (std::nothrow) CasDataPipe();
    if (m_virtualDeviceStream == nullptr) {
        ERR("Failed to new virtual device packet stream.");
        return false;
    }

    m_imeDataStream = new (std::nothrow) CasDataPipe();
    if (m_imeDataStream == nullptr) {
        ERR("Failed to new ime data packet stream.");
        return false;
    }

    return true;
}

bool CasController::ClearDataStream()
{
    if (m_audioPacketStream != nullptr) {
        m_audioPacketStream->Clear();
    }

    if (m_videoPacketStream != nullptr) {
        m_videoPacketStream->Clear();
    }

    if (m_controlStream != nullptr) {
        m_controlStream->Clear();
    }

    if (m_orientationStream != nullptr) {
        m_orientationStream->Clear();
    }

    if (m_channelStream != nullptr) {
        m_channelStream->Clear();
    }

    if (m_virtualDeviceStream != nullptr) {
        m_virtualDeviceStream->Clear();
    }

    if (m_imeDataStream != nullptr) {
        m_imeDataStream->Clear();
    }

    INFO("Succeed to clear data stream ");
    return true;
}

bool CasController::CloseDataStream()
{
    if (m_audioPacketStream != nullptr) {
        delete m_audioPacketStream;
        m_audioPacketStream = nullptr;
    }

    if (m_videoPacketStream != nullptr) {
        delete m_videoPacketStream;
        m_videoPacketStream = nullptr;
    }

    if (m_controlStream != nullptr) {
        delete m_controlStream;
        m_controlStream = nullptr;
    }

    if (m_orientationStream != nullptr) {
        delete m_orientationStream;
        m_orientationStream = nullptr;
    }

    if (m_channelStream != nullptr) {
        delete m_channelStream;
        m_channelStream = nullptr;
    }

    if (m_imeDataStream != nullptr) {
        delete m_imeDataStream;
        m_imeDataStream = nullptr;
    }

    INFO("Succeed to close data stream ");
    return true;
}

uint64_t CasController::GetLag()
{
    if (m_heartbeatThread != nullptr) {
        return m_heartbeatThread->GetLag();
    }
    return 0;
}

bool CasController::SendTouchEvent(int id, int action, int x, int y, int pressure, long time, int orientation, int height, int width)
{
    int new_time = (int) time;
    std::lock_guard<std::mutex> lockGuard(this->m_lock);
    if (this->GetState() == STOPPED) {
        INFO("Failed to send touch event because phone already stop.");
        return false;
    }
    if (m_casClientSocket == nullptr || m_casClientSocket->GetStatus() != SOCKET_STATUS_RUNNING) {
        ERR("Failed to send touch event because socket status not running.");
        return false;
    }

    if (m_touch == nullptr) {
        return false;
    }
    return m_touch->SendTouchEvent(id, action, x, y, pressure, new_time, orientation, height, width);
}

bool CasController::SendKeyEvent(uint16_t keycode, uint16_t action)
{
    std::lock_guard<std::mutex> lockGuard(this->m_lock);
    if (this->GetState() == STOPPED) {
        INFO("Failed to send key event because phone already stop.");
        return false;
    }
    if (m_casClientSocket == nullptr || m_casClientSocket->GetStatus() != SOCKET_STATUS_RUNNING) {
        ERR("Failed to send key event because socket status not running.");
        return false;
    }

    if (m_touch == nullptr) {
        return false;
    }
    return m_touch->SendKeyEvent(keycode, action);
}

bool CasController::SendMotionEvent(uint16_t masterAxis, int32_t masterValue, uint16_t secondaryAxis,
    int32_t secondaryValue)
{
    std::lock_guard<std::mutex> lockGuard(this->m_lock);
    if (this->GetState() == STOPPED) {
        INFO("Failed to send motion event because phone already stop.");
        return false;
    }
    if (m_casClientSocket == nullptr || m_casClientSocket->GetStatus() != SOCKET_STATUS_RUNNING) {
        ERR("Failed to send motion event because socket status not running.");
        return false;
    }

    if (m_touch == nullptr) {
        return false;
    }

    return m_touch->SendMotionEvent(masterAxis, masterValue, secondaryAxis, secondaryValue);
}

int CasController::JniSendData(CasMsgType type, uint8_t *data, int length)
{
    std::lock_guard<std::mutex> lockGuard(this->m_lock);
    if (m_streamBuildSender == nullptr) {
        return -1;
    }
    return length == m_streamBuildSender->SendDataToServer(type, data, length);
}

int CasController::JniRecvData(CasMsgType type, uint8_t *data, int length)
{
    void *pPkt = nullptr;
    bool isGetOrientation = false;
    switch (type) {
        case CasMsgType::Audio:
            if (m_audioPacketStream != nullptr) {
                pPkt = m_audioPacketStream->GetNextPkt();
            }
            break;
        case CasMsgType::Orientation:
            if (m_orientationStream != nullptr) {
                pPkt = m_orientationStream->GetNextPkt();
                isGetOrientation = true;
            }
            break;
        case CasMsgType::Channel:
            if (m_channelStream != nullptr) {
                pPkt = m_channelStream->GetNextPkt();
            }
            break;
        case CasMsgType::VirtualDevice:
            if (m_virtualDeviceStream != nullptr) {
                pPkt = m_virtualDeviceStream->GetNextPkt();
            }
            break;
        case CasMsgType::ImeData:
            if (m_imeDataStream != nullptr) {
                pPkt = m_imeDataStream->GetNextPkt();
            }
            break;
        default:
            ERR("Invalid type %d, length %d.", type, length);
            return 0;
    }

    if (pPkt == nullptr) {
        DBG("Packet is null.");
        return 0;
    }
    stream_msg_head_t *streamMsgHead = (stream_msg_head_t *)pPkt;
    unsigned int dataLen = streamMsgHead->GetPayloadSize();
    char *pPayload = (char *)pPkt + sizeof(stream_msg_head_t);

    if ((unsigned int)length < dataLen) {
        ERR("Input buffer is not large enough, type %d free %d payload %u", streamMsgHead->type, length, dataLen);
        return 0;
    }

    if (EOK != memcpy_s(data, dataLen, pPayload, dataLen)) {
        ERR("Failed to copy.");
    }

    if (isGetOrientation) {
        IsNeedRotation((int) (*pPayload));
    }

    return dataLen;
}

void CasController::IsNeedRotation(int orientation)
{

    INFO("ORIENTATION is %d, old ORIENTATION is %d.", orientation, m_orientation);
    if (orientation == 8) {
        m_rotationDegrees = 270;
    } else if (orientation == 24) {
        m_rotationDegrees = 90;
    } else if (orientation == 16) {
        m_rotationDegrees = 180;
    } else {
        m_rotationDegrees = 0;
    }

    std::lock_guard<std::mutex> lockGuard(this->m_decoderLock);
    ResetDecoder(m_isNotifyFirstFrame);
    ForceIFrame();
    m_orientation = orientation;
}

bool CasController::GetConnectStatus()
{
#if defined(RECONNECT) || (SOCKET_RECONNECT)
    if (m_casClientSocket != nullptr) {
        int status = m_casClientSocket->GetStatus();
        if (status == SOCKET_STATUS_RUNNING) {
            return true;
        }
    }
    return false;
#else
    // always return true to avoid reconnect dialog
    return true;
#endif
}

void CasController::ProcessEnterBackground()
{
    if (m_sessionId.empty()) {
        ERR("SessionId is empty.");
        return;
    }

    string pauseCmd = CMD_PAUSE;
    map<string, string> parameters = { { KEY_COMMAND, pauseCmd }, { KEY_SESSION_ID, m_sessionId } };

    SendCommand(parameters);
    StopDecWorker(!m_retainVideoDecode);
    ClearDataStream();
}

bool CasController::ProcessEnterForeground(ANativeWindow *nativeWindow)
{
    ClearDataStream();

    if (m_sessionId.empty()) {
        ERR("SessionId is empty.");
        return false;
    }

    std::string resumeCmd = CMD_RESUME;
    map<string, string> parameters = { { KEY_COMMAND, resumeCmd }, { KEY_SESSION_ID, m_sessionId } };

    bool res = SendCommand(parameters);
    if (!res) {
        ERR("Failed to send resume command");
        return false;
    }
    return true;
}

bool CasController::CreateDecWorker(ANativeWindow *nativeWindow, bool needVideoDecode)
{
    if (needVideoDecode) {
        m_videoDecodeThread = new (std::nothrow) CasVideoHDecodeThread(nativeWindow, m_frameType, m_rotationDegrees);
        if (m_videoDecodeThread == nullptr) {
            ERR("Failed to new video decode thread.");
            return false;
        }
        m_videoDecodeThread->SetDecodePktHandle(m_videoPacketStream);
    }
    return true;
}

void CasController::StartDecWorker(bool retainVideoDecode)
{
    if (retainVideoDecode) {
        if (m_videoDecodeThread != nullptr) {
            m_videoDecodeThread->Restart();
        }
    } else {
        m_videoDecodeThread->Start();
    }
}

void CasController::StopDecWorker(bool retainVideoDecode)
{
    if (m_videoDecodeThread == nullptr) {
        return;
    }

    if (retainVideoDecode) {
        m_videoDecodeThread->Stop();
    } else {
        m_videoDecodeThread->Exit();
        delete m_videoDecodeThread;
        m_videoDecodeThread = nullptr;
    }
}

bool CasController::ForceIFrame()
{
    std::lock_guard<std::mutex> lockGuard(this->m_lock);
    if (this->GetState() == STOPPED) {
        INFO("Force IFrame failed because phone already stop.");
        return false;
    }

    if (m_casClientSocket == nullptr || m_casClientSocket->GetStatus() != SOCKET_STATUS_RUNNING) {
        ERR("Force IFrame failed because socket status not running.");
        return false;
    }

    map<string, string> parameters = { { KEY_COMMAND, CMD_REQ_IFRAME }, { KEY_SESSION_ID, m_sessionId } };
    bool res = SendCommand(parameters);
    if (!res) {
        ERR("Send Command request i frame failed.");
        return false;
    }
    return true;
}

void CasController::NotifyCommand(int type, string msg)
{
    std::lock_guard<std::mutex> lockGuard(this->m_callbackLock);
    if (cmdCallBack != nullptr) {
        cmdCallBack(type, std::move(msg));
    }
}

void CasController::NotifyFirstVideoFrame()
{
    std::lock_guard<std::mutex> lockGuard(this->m_callbackLock);
    if (!m_isNotifyFirstFrame) {
        m_isNotifyFirstFrame = true;
        cmdCallBack(CAS_FIRST_FRAME, CasMsgCode::GetMsg(CAS_FIRST_FRAME));
    }
}

void CasController::OnCmdRecv(int code, string msg)
{
    if (code == CAS_H265_NOT_SUPPORT) {
        m_frameType = FrameType::H264;
        m_mediaConfig[KEY_FRAME_TYPE] = "h264";
        if (!SendStartCmd()) {
            this->SetState(START_FAILURE);
            NotifyCommand(CAS_ENGINE_START_FAILED, CasMsgCode::GetMsg(CAS_ENGINE_START_FAILED));
            ERR("Failed to send start command");
        }
    } else {
        NotifyCommand(code, msg);
    }
}

void CasController::ResetDecoder(bool isClearStream)
{
    StopDecWorker(false);
    if (isClearStream) {
        ClearDataStream();
    }
    CreateDecWorker(m_nativeWindow, m_needVideoDecode);
    StartDecWorker(false);
}

bool CasController::SendStartCmd()
{
    std::string mediaConfigStr = "";
    if (IsValidMediaConfig(m_mediaConfig)) {
        mediaConfigStr = CasAppCtrlCmdUtils::MakeCommand(m_mediaConfig, SUB_COMMAND_SEPARATOR);
    }
    std::string startCmd = CMD_START_APP;
    map<string, string> parameters = { { KEY_COMMAND, startCmd },
                                       { KEY_TICKET, m_ticket },
                                       { KEY_AUTH_TS, m_authTs },
                                       { KEY_VERIFY_DATA, m_verifyData },
                                       { KEY_ENCRYPTED_DATA, m_encryptedData },
                                       { KEY_SESSION_ID, m_sessionId },
                                       { KEY_AES_IV, m_aesIv },
                                       { KEY_SDK_VERSION, m_conf.sdkVersion },
                                       { KEY_PROTOCOL_VERSION, m_conf.protocolVersion },
                                       { KEY_CLIENT_TYPE, m_clientType },
                                       { KEY_MEDIA_CONFIG, mediaConfigStr },
                                       { KEY_MAX_DISCONNECT_DURATION, m_maxDisconnectDuration } };

    return SendCommand(parameters);
}